﻿// <copyright file="Vector.cs" company="Community Combat Simulator">
//    Community Combat Simulator: a free combat flight simulator.
//    Copyright (C) 2008 Aaron Schinkowitch
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see http://www.gnu.org/licenses/.
// </copyright>
namespace CCSim.Vecmath
{
    using System;
    using System.Globalization;
    using CCSim.Util;

    /// <summary>
    /// A 3-dimensional vector in Cartesian coordinate space, using double precision.
    /// </summary>
    public class Vector
    {
        /// <summary>
        /// A component of the vector.
        /// </summary>
        private double x, y, z;
        
        /// <summary>
        /// Initializes a new instance of the Vector class with initial component values.
        /// </summary>
        /// <param name="x">The x-component.</param>
        /// <param name="y">The y-component.</param>
        /// <param name="z">The z-component.</param>
        public Vector(double x, double y, double z)
        {
            this.X = x;
            this.Y = y;
            this.Z = z;
        }
        
        /// <summary>
        /// Initializes a new instance of the Vector class with all zero components.
        /// </summary>
        public Vector()
        {
        }
        
        /// <summary>
        /// Initializes a new instance of the Vector class with the same component values as the 
        /// specified vector.
        /// </summary>
        /// <param name="other">The vector whose component values are copied.</param>
        public Vector(Vector other) 
        {
            this.SetComponents(other);
        }
        
        /// <summary>
        /// Gets or sets the vector's x component.
        /// </summary>
        /// <value>The vector's x component.</value>
        public double X 
        {
            get 
            { 
                return this.x; 
            }
            
            set
            {
                this.CheckComponentValue("X", value);
                this.x = value;
            }
        }
        
        /// <summary>
        /// Gets or sets the vector's y component.
        /// </summary>
        /// <value>The vector's y component.</value>
        public double Y 
        {
            get 
            { 
                return this.y; 
            }
            
            set
            {
                this.CheckComponentValue("Y", value);
                this.y = value;
            }
        }
        
        /// <summary>
        /// Gets or sets the vector's z component.
        /// </summary>
        /// <value>The vector's z component.</value>
        public double Z 
        {
            get 
            { 
                return this.z; 
            }
            
            set
            {
                this.CheckComponentValue("Z", value);
                this.z = value;
            }
        }
        
        /// <summary>
        /// Gets the square of the vector's length.
        /// </summary>
        /// <value>The square of the vector's length.</value>
        public double LengthSquared 
        {
            get { return (this.X * this.X) + (this.Y * this.Y) + (this.Z * this.Z); }
        }

        /// <summary>
        /// Gets a new vector that has the same direction as the vector, but is unit length.
        /// </summary>
        /// <value>A new vector that is the vector's normal.</value>
        public Vector Normal
        {
            get { return new Vector(this).Normalize(); }
        }

        /// <summary>
        /// Scales a vector, returning a new vector.
        /// </summary>
        /// <param name="vector">The vector to scale.</param>
        /// <param name="scalar">The scalar.</param>
        /// <returns>A new vector that is the vector scaled.</returns>
        public static Vector operator *(Vector vector, double scalar)
        {
            if (vector == null) 
            {
                throw new ArgumentNullException("vector");
            }
            
            return new Vector(vector).Scale(scalar);
        }
        
        /// <summary>
        /// Adds two vectors, returning the result as a new vector.
        /// </summary>
        /// <param name="left">The first operand vector.</param>
        /// <param name="right">The second operand vector.</param>
        /// <returns>The sum, as a new vector.</returns>
        public static Vector operator +(Vector left, Vector right) 
        {
            if (left == null)
            {
                throw new ArgumentNullException("left");
            }

            if (right == null)
            {
                throw new ArgumentNullException("right");
            }
            
            return new Vector(left).Add(right);
        }
        
        /// <summary>
        /// Computes the difference between two vectors, returning the result as a new vector.
        /// </summary>
        /// <param name="left">The first operand.</param>
        /// <param name="right">The second operand.</param>
        /// <returns>The difference as a new vector.</returns>
        public static Vector operator -(Vector left, Vector right)
        {
            if (left == null)
            {
                throw new ArgumentNullException("left");
            }
            
            if (right == null)
            {
                throw new ArgumentNullException("right");
            }
            
            return new Vector(left.X - right.X, left.Y - right.Y, left.Z - right.Z);
        }
        
        /// <summary>
        /// Performs Matrix-Vector multiplication.
        /// </summary>
        /// <param name="left">The matrix.</param>
        /// <param name="right">The vector.</param>
        /// <returns>A new vector containing the result of the multiplication.</returns>
        public static Vector operator *(Matrix left, Vector right)
        {
            if (left == null) 
            {
                throw new ArgumentNullException("left", "Left operand (matrix) cannot be null");
            }
            
            if (right == null)
            {
                throw new ArgumentNullException("right", "Right operand (vector) cannot be null");
            }
            
            Vector result = new Vector(right);
            
            result.LeftMultiply(left);
            
            return result;
        }
                
        /// <summary>
        /// Sets the component values to those of the spesified vector.
        /// </summary>
        /// <param name="other">The vector whose component values are copied.</param>
        /// <returns>The vector itself.</returns>
        public Vector SetComponents(Vector other) 
        {
            if (other == null) 
            {
                throw new ArgumentNullException("other");
            }
            
            this.x = other.x;
            this.y = other.y;
            this.z = other.z;
            
            return this;
        }

        /// <summary>
        /// Test for equality. Vectors are equal if all their components are equal.
        /// </summary>
        /// <param name="obj">The object with which to check for equality.</param>
        /// <returns>true if the passed object is equal to the vector, false otherwise.</returns>
        public override bool Equals(object obj)
        {
            Vector other = obj as Vector;
            
            Equality equality = new Equality();
            
            return other != null
                && equality.RoughlyEqual(this.X, other.X)
                && equality.RoughlyEqual(this.Y, other.Y) 
                && equality.RoughlyEqual(this.Z, other.Z);
        }
        
        /// <summary>
        /// Gets a hash code for the vector.
        /// </summary>
        /// <returns>The hash code.</returns>
        public override int GetHashCode()
        {
            return this.X.GetHashCode() ^ this.Y.GetHashCode() ^ this.Z.GetHashCode();
        }
        
        /// <summary>
        /// Gets a string representation of the vector.
        /// </summary>
        /// <returns>A string representation of the vector.</returns>
        public override string ToString()
        {
            return String.Format(
                CultureInfo.InvariantCulture, 
                "{0} (x: {1} y: {2} z: {3})", 
                GetType().Name, 
                this.X, 
                this.Y, 
                this.Z);
        }
        
        /// <summary>
        /// Performs inline scaling of the vector. Each of the vector's components are scaled
        /// by the passed scalar.
        /// </summary>
        /// <param name="scalar">The scalar.</param>
        /// <returns>The vector.</returns>
        public Vector Scale(double scalar)
        {
            if (Double.IsNaN(scalar) || Double.IsInfinity(scalar)) 
            {
                throw new ArgumentOutOfRangeException("scalar", "scalar: " + scalar);
            }
            
            this.X *= scalar;
            this.Y *= scalar;
            this.Z *= scalar;
            
            return this;
        }
        
        /// <summary>
        /// Performs inline vector addition. The components of the vector are updated to be the
        /// result of adding the passed vector to the vector.
        /// </summary>
        /// <param name="right">The value to which to add to the vector.</param>
        /// <returns>The vector itself.</returns>
        public Vector Add(Vector right)
        {
            this.X += right.X;
            this.Y += right.Y;
            this.Z += right.Z;
            
            return this;
        }
        
        /// <summary>
        /// Computes the dot product of the vector and another vector.
        /// </summary>
        /// <param name="right">The other vector.</param>
        /// <returns>The dot product.</returns>
        public double Dot(Vector right)
        {
            if (right == null) 
            {
                throw new ArgumentNullException("right");
            }
            
            return (this.X * right.X) + (this.Y * right.Y) + (this.Z * right.Z);
        }
        
        /// <summary>
        /// Performs the cross product of two vectors inline. The components of the
        /// vector are set to the cross product of the vector with the passed vector.
        /// <para/>
        /// this = this x right.
        /// </summary>
        /// <param name="right">The right hand vector for the cross product.</param>
        /// <returns>The vector itself.</returns>
        public Vector Cross(Vector right) 
        {
            if (right == null) 
            {
                throw new ArgumentNullException("right");
            }
            
            // (a2b3 − a3b2) i + (a3b1 − a1b3) j + (a1b2 − a2b1) k
            double x = (this.Y * right.Z) - (this.Z * right.Y);
            double y = (this.Z * right.X) - (this.X * right.Z);
            this.Z = (this.X * right.Y) - (this.Y * right.X);
            
            this.X = x;
            this.Y = y;
            
            return this;
        }
        
        /// <summary>
        /// Checks if the vector has length zero.
        /// </summary>
        /// <returns>true if the vector has length zero, false otherwise.</returns>
        public bool IsZero()
        {
            return this.X == 0.0 && this.Y == 0.0 && this.Z == 0.0;
        }
                
        /// <summary>
        /// Performs inline normalization of the vector. The vector's components are scaled so that 
        /// the vector has the same direction, but is of unit length.
        /// </summary>
        /// <returns>The vector itself.</returns>
        public Vector Normalize()
        {
            if (this.IsZero()) 
            {
                throw new InvalidOperationException("Can't normalize zero vector");
            }
            
            return this.Scale(1.0 / Math.Sqrt(this.LengthSquared));
        }
        
        /// <summary>
        /// Checks if the vector is normalized. A normalized vector has length 1.
        /// </summary>
        /// <returns>true if the vector is normalized, false otherwise.</returns>
        public bool IsNormalized()
        {
            return Math.Abs(1.0 - this.LengthSquared) < 1E-6;
        }
        
        /// <summary>
        /// Sets the vector's components to the absolute value of the vector inline.
        /// </summary>
        public void Abs()
        {
            this.X = Math.Abs(this.X);
            this.Y = Math.Abs(this.Y);
            this.Z = Math.Abs(this.Z);
        }
        
        /// <summary>
        /// Calculates the transformation of the vector, first rotating then translating, storing
        /// the result in the supplied output vector.
        /// </summary>
        /// <param name="rotation">The rotation matrix.</param>
        /// <param name="translation">The translation vector.</param>
        /// <param name="result">The vector in which to store the result.</param>
        /// <returns>The result vector.</returns>
        public Vector Transform(Matrix rotation, Vector translation, Vector result)
        {
            if (rotation == null)
            {
                throw new ArgumentNullException("rotation");
            }
            
            if (translation == null)
            {
                throw new ArgumentNullException("translation");
            }
            
            if (result == null)
            {
                throw new ArgumentNullException("result");
            }
            
            result.SetComponents(this);            
            result.LeftMultiply(rotation);
            result.Add(translation);
            
            return result;
        }

        /// <summary>
        /// Checks a value can be as one of the vector's components.
        /// </summary>
        /// <param name="component">The component being set.</param>
        /// <param name="value">The value being set.</param>
        private void CheckComponentValue(string component, double value)
        {
            if (Double.IsNaN(value) || Double.IsInfinity(value))
            {
                throw new ArgumentOutOfRangeException(
                    "value", "Cannot set " + component + " to " + value);
            }
        }

        /// <summary>
        /// Multiplies the vector by a matrix.
        /// </summary>
        /// <param name="left">The left-hand matrix.</param>
        private void LeftMultiply(Matrix left)
        {
            double x = (left.M00 * this.X) + (left.M01 * this.Y) + (left.M02 * this.Z);
            double y = (left.M10 * this.X) + (left.M11 * this.Y) + (left.M12 * this.Z);
            
            this.Z = (left.M20 * this.X) + (left.M21 * this.Y) + (left.M22 * this.Z);
            this.X = x;
            this.Y = y;
        }
    }
}